import threading
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import *

import helper
import settings
import visualize
from backtest import BackTest
from data import Data
from strategy import Strategy


class App:
    def __init__(self, root_window):
        self.window = root_window

        self.dataset_path = tk.StringVar()  # 数据集路径
        self.start_date_string = tk.StringVar()  # 起始时间
        self.end_date_string = tk.StringVar()  # 结束时间
        self.strategy_name = tk.StringVar()  # 策略名称

        self.roa = tk.StringVar()  # 资产回报率
        self.maximum_draw_down = tk.StringVar()  # 最大回撤率
        self.sharpe_ratio = tk.StringVar()  # 夏普比率

        self.data_frame = None  # 绘图用
        self.status = tk.StringVar()  # 回测状态
        self.load_status = tk.StringVar()   # 加载数据状态

        self.cleaned_data = {}
        self.stock_name_indexed_data = {}
        self.valid_stocks = None

        # 选择数据集
        self.dataset_lbl = tk.Label(self.window, text="选择数据集", font=(settings.FONT, 11), bg=background)

        self.dataset_selector = ttk.Combobox(self.window)
        self.dataset_selector['value'] = ('日频数据', '周频数据')
        self.dataset_selector.bind("<<ComboboxSelected>>", self.select_dataset)

        self.load_dataset_btn = tk.Button(self.window, text="加载数据", font=(settings.FONT, 11), bg="#fafaf6",
                                          bd=1, relief="groove", command=lambda: self.thread_it(self.load_data))
        self.load_status_text = tk.Label(self.window, textvariable=self.load_status, font=(settings.FONT, 11), bg=background)


        # 选择起止时间
        self.start_date_lbl = tk.Label(self.window, text="输入开始时间", font=(settings.FONT, 11), bg=background)
        self.end_date_lbl = tk.Label(self.window, text="输入结束时间", font=(settings.FONT, 11), bg=background)

        self.start_date_input = tk.Entry(self.window, width=25, bg="white", fg="#777777",
                                         textvariable=self.start_date_string)
        self.start_date_string.set('2010-03-19')
        self.end_date_input = tk.Entry(self.window, width=25, bg="white", fg="#777777",
                                       textvariable=self.end_date_string)
        self.end_date_string.set('2010-05-30')
        self.check_date_btn = tk.Button(self.window, text="检测日期格式", font=(settings.FONT, 11), bg="#fafaf6",
                                        bd=1, relief="groove", command=self.check_date_format)

        # 选择策略
        self.strategy_lbl = tk.Label(self.window, text="选择策略", font=(settings.FONT, 11), bg=background)

        self.strategy_selector = ttk.Combobox(self.window)
        self.strategy_selector['value'] = ('BBI', 'BOLL', 'PSY', 'RSI', 'MUL')
        self.strategy_selector.bind("<<ComboboxSelected>>", self.select_strategy)

        self.back_test_run = tk.Button(self.window, text="开始回测", font=(settings.FONT, 11), bg="#fafaf6",
                                       bd=1, relief="groove", command=lambda: self.thread_it(self.run_back_test))
        self.status_text = tk.Label(self.window, textvariable=self.status, font=(settings.FONT, 11), bg=background)


        # 展示有效股票
        self.valid_stocks_lbl = tk.Label(self.window, text="有效股票", font=(settings.FONT, 11), bg=background)

        self.stock_selector = ttk.Combobox(self.window)
        self.stock_selector['value'] = ('None')
        self.stock_selector.bind("<<ComboboxSelected>>", self.select_stock)

        # 回测结果 股票收益率 最大回撤率 夏普比率
        self.roa_lbl = tk.Label(self.window, text="股票收益率：", font=(settings.FONT, 11), bg=background)
        self.roa_txt = tk.Label(self.window, textvariable=self.roa, font=(settings.FONT, 11), bg=background)
        self.maximum_draw_down_lbl = tk.Label(self.window, text="最大回撤率：", font=(settings.FONT, 11), bg=background)
        self.maximum_draw_down_txt = tk.Label(self.window, textvariable=self.maximum_draw_down,
                                              font=(settings.FONT, 11), bg=background)
        self.sharpe_ratio_lbl = tk.Label(self.window, text="夏普比率：", font=(settings.FONT, 11), bg=background)
        self.sharpe_ratio_txt = tk.Label(self.window, textvariable=self.sharpe_ratio, font=(settings.FONT, 11),
                                         bg=background)

        # 绘图

        self.draw_daily_yield_btn = tk.Button(self.window, text="收益率曲线", font=(settings.FONT, 11), bg="#fafaf6",
                                              bd=1, relief="groove",
                                              command=lambda: self.thread_it(self.draw_daily_yield_chart))
        self.draw_kline_btn = tk.Button(self.window, text="绘制K线图", font=(settings.FONT, 11), bg="#fafaf6",
                                        bd=1, relief="groove",
                                        command=lambda: self.thread_it(self.draw_kline_chart))

        # 布局
        self.dataset_lbl.grid(column=0, row=0, padx=10)
        self.dataset_selector.grid(column=1, row=0, padx=10)

        self.start_date_lbl.grid(column=0, row=1, padx=10)
        self.start_date_input.grid(column=1, row=1, padx=10)
        self.end_date_lbl.grid(column=0, row=2, padx=10, pady=5)
        self.end_date_input.grid(column=1, row=2, padx=10, pady=5)
        self.check_date_btn.grid(column=2, row=2)

        self.strategy_lbl.grid(column=0, row=3, padx=10, pady=3)
        self.strategy_selector.grid(column=1, row=3, padx=10, pady=3)

        self.back_test_run.grid(column=0, row=4, padx=10, pady=3)
        self.status_text.grid(column=1, row=4, padx=15)

        self.roa_lbl.grid(column=0, row=5, padx=10, pady=3)
        self.roa_txt.grid(column=1, row=5)
        self.maximum_draw_down_lbl.grid(column=0, row=6, padx=10, pady=3)
        self.maximum_draw_down_txt.grid(column=1, row=6)
        self.sharpe_ratio_lbl.grid(column=0, row=7, padx=10, pady=3)
        self.sharpe_ratio_txt.grid(column=1, row=7)

        self.load_dataset_btn.grid(column=0, row=8)
        self.load_status_text.grid(column=1, row=8, padx=15)

        self.valid_stocks_lbl.grid(column=0, row=9, padx=10, pady=3)
        self.stock_selector.grid(column=1, row=9, padx=10, pady=3)

        self.draw_daily_yield_btn.grid(column=0, row=10, pady=3)
        self.draw_kline_btn.grid(column=1, row=10, pady=3)

    def select_dataset(self, event):
        selected_dataset = self.dataset_selector.get()
        print(selected_dataset)
        if selected_dataset == '日频数据':
            self.dataset_path.set(settings.DAILY_DATA_PATH)
        if selected_dataset == '周频数据':
            self.dataset_path.set(settings.WEEKLY_DATA_PATH)

    def check_date_format(self):
        self.start_date_string.set(self.start_date_input.get())
        self.end_date_string.set(self.end_date_input.get())

    def select_strategy(self, event):
        print(self.strategy_selector.get())
        self.strategy_name.set(self.strategy_selector.get())

    def load_data(self):
        self.load_status.set("加载中")
        stocks_data = Data()
        stocks_data.read_pickle_as_dict(self.dataset_path.get())
        stocks_data.get_raw_data_between(self.start_date_string.get(), self.end_date_string.get())
        stocks_data.prepare_data_for_factor(settings.FACTORS[self.strategy_name.get()])
        self.cleaned_data = stocks_data.cleaned_data
        # 转置索引 日期 -> 股票名称
        self.stock_name_indexed_dict = helper.change_dict_keys(self.cleaned_data)

        self.valid_stocks = list(self.stock_name_indexed_dict.keys())
        print("加载完成")
        # 更新股票选择器的值列表
        self.stock_selector['value'] = self.valid_stocks
        self.load_status.set("加载完成")

    def select_stock(self, event):
        print(self.stock_selector.get())
        self.strategy_name.set(self.strategy_selector.get())

    @staticmethod
    def thread_it(func, *args):
        """将函数打包进线程"""
        t = threading.Thread(target=func, args=args)  # 创建
        t.setDaemon(True)  # 守护 !!!
        t.start()  # 启动
        # 阻塞--卡死界面！
        # t.join()

    def draw_daily_yield_chart(self):
        chart_name = visualize.draw_yield_line(self.data_frame,
                                               self.roa.get(),
                                               self.maximum_draw_down.get(),
                                               self.sharpe_ratio.get())
        helper.show_charts(chart_name)

    def draw_kline_chart(self):
        kline_df = self.stock_name_indexed_dict[self.stock_selector.get()]
        chart_name = visualize.draw_kline(kline_df)
        helper.show_charts(chart_name)

    def run_back_test(self):
        if self.dataset_path.get() == '':
            showinfo('提示', '请选择数据集')
            return
        if self.strategy_name.get() == '':
            showinfo('提示', '请选择策略')
            return
        print("回测中")
        self.status.set("回测中")
        stocks_data = Data()
        stocks_data.read_pickle_as_dict(self.dataset_path.get())
        print("读入数据完成")
        stocks_data.get_raw_data_between(self.start_date_string.get(), self.end_date_string.get())
        stocks_data.prepare_data_for_factor(settings.FACTORS[self.strategy_name.get()])
        print("数据清洗完成")

        test_dict = Strategy.Select_Strategy(self.strategy_name.get(), stocks_data.cleaned_data)
        back_test = BackTest()
        print("开始回测")
        back_test.run_back_test_with_stocks(test_dict)

        print("{:.2%}".format(back_test.roa))
        self.data_frame = back_test.data_frame
        self.roa.set("{:.2%}".format(back_test.roa))
        self.maximum_draw_down.set("{:.2%}".format(back_test.max_draw_down))
        self.sharpe_ratio.set("%.2f" % back_test.sharp_ratio)
        self.status.set("回测结束")


if __name__ == '__main__':
    window = tk.Tk()

    windowWidth = settings.WIDTH
    windowHeight = settings.HEIGHT
    screenWidth = window.winfo_screenwidth()
    screenHeight = window.winfo_screenheight()

    x = int((screenWidth - windowWidth) / 2)
    y = int((screenHeight - windowHeight) / 2)

    window.geometry("%sx%s+%s+%s" % (windowWidth, windowHeight, x, y))
    window.resizable(False, False)

    window.title("GUI window")
    background = "#fbfbfb"
    window.configure(bg=background)

    app = App(window)

    window.mainloop()
